package jaygoo.widget.wlv;

import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;
import ohos.global.resource.NotExistException;
import ohos.global.resource.WrongTypeException;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * ================================================
 * 作    者：JayGoo
 * 版    本：1.0.0
 * 创建日期：2017/7/21
 * 描    述: 绘制波浪曲线
 * ================================================
 */
public class WaveLineView extends RenderView implements Component.DrawTask {
    private static HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x000110, "GWaveLineView");
    private static HiLogLabel label1 = new HiLogLabel(HiLog.LOG_APP, 0x000110, "GWaveLineView1");
    private final static int DEFAULT_SAMPLING_SIZE = 64;
    private final static float DEFAULT_OFFSET_SPEED = 250F;
    private final static int DEFAULT_SENSIBILITY = 5;

    //采样点的数量，越高越精细，但是高于一定限度肉眼很难分辨，越高绘制效率越低
    private int samplingSize;

    //控制向右偏移速度，越小偏移速度越快
    private float offsetSpeed;
    //平滑改变的音量值
    private float volume = 0;

    //用户设置的音量，[0,100]
    private int targetVolume = 50;

    //每次平滑改变的音量单元
    private float perVolume;

    //灵敏度，越大越灵敏[1,10]
    private int sensibility;

    //背景色
    private int canvasbackGroundColor = -1;
    private Color backGroundColor = Color.WHITE;
    //波浪线颜色
    private int lineColor;
    //粗线宽度
    private int thickLineWidth;
    //细线宽度
    private int fineLineWidth;

    private final Paint paint = new Paint();

    {
        //防抖动
        paint.setDither(true);
        //抗锯齿，降低分辨率，提高绘制效率
        paint.setAntiAlias(true);
    }

    private List<Path> paths = new ArrayList<>();
    private List<Path> temp_paths = new ArrayList<>();

    {
        for (int i = 0; i < 4; i++) {
            paths.add(new Path());
        }
    }

    //不同函数曲线系数
    private float[] pathFuncs = {
        0.6f, 0.35f, 0.1f, -0.1f
    };

    //采样点X坐标
    private float[] samplingX;
    //采样点位置映射到[-2,2]之间
    private float[] mapX;
    //画布宽高
    private int width, height;
    //画布中心的高度
    private int centerHeight;
    //振幅
    private float amplitude;
    //存储衰变系数
    private Map<Integer,Double> recessionFuncs1 = new HashMap<Integer, Double>();

    //连线动画结束标记
    private boolean isPrepareLineAnimEnd = false;
    //连线动画位移
    private int lineAnimX = 0;
    //渐入动画结束标记
    private boolean isPrepareAlphaAnimEnd = false;
    //渐入动画百分比值[0,1f]
    private float prepareAlpha = 0f;
    //是否开启准备动画
    private boolean isOpenPrepareAnim = false;

    private boolean isTransparentMode = false;
    private Context mContext;

    public WaveLineView(Context context) {
        this(context, null);
    }

    public WaveLineView(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    public WaveLineView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        try {
            mContext = context;
            mHMEventHandler = new HMEventHandler(runner);
            initAttr(attrs);
        } catch (NotExistException e) {
            e.printStackTrace();
        } catch (WrongTypeException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initAttr(AttrSet attrs) throws NotExistException, WrongTypeException, IOException {
        canvasbackGroundColor = Color.getIntColor(attrs.getAttr("wlvBackgroundColor").get().getStringValue());
        backGroundColor = attrs.getAttr("wlvBackgroundColor").get().getColorValue();
        samplingSize = attrs.getAttr("wlvSamplingSize").get().getIntegerValue();
        lineColor = Color.getIntColor(attrs.getAttr("wlvLineColor").get().getStringValue());
        thickLineWidth = attrs.getAttr("wlvThickLineWidth").get().getIntegerValue();
        fineLineWidth = attrs.getAttr("wlvFineLineWidth").get().getIntegerValue();
        offsetSpeed = attrs.getAttr("wlvMoveSpeed").get().getFloatValue();
        sensibility = attrs.getAttr("wlvSensibility").get().getIntegerValue();
        isTransparentMode = backGroundColor == Color.TRANSPARENT;
        checkVolumeValue();
        checkSensibilityValue();
        //将RenderView放到最顶层
        pinToZTop(true);
        if (getSurfaceOps().get()!=null){
            getSurfaceOps().get().setFormat(-3);
        }
        addDrawTask(this);
    }

    @Override
    protected void doDrawBackground(Canvas canvas) {
        HiLog.info(label, "WLV_doDrawBackground first");
        int eventId = 2;
        long param = millisPassed;
        Object object = (Object) EventRunner.current();
        InnerEvent event = InnerEvent.get(eventId,  param, object);
        mHMEventHandler.sendEvent(event);
    }

    private boolean isParametersNull() {
        if (null == samplingX || null == mapX || null == pathFuncs) {
            return true;
        }
        return false;
    }

    private  long millisPassed;
    //动画是否被启动过
    private  boolean isStart = false;
    //是否立即停止
    private static volatile boolean isStop = false;

    public  boolean isIsStart() {
        return isStart;
    }

    public  void setIsStart(boolean isStart) {
        isStart = isStart;
    }

    private static volatile boolean isRelease = false;
    public long getMillisPassed() {
        return millisPassed;
    }

    public void setMillisPassed(long millisPassed) {
        this.millisPassed = millisPassed;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        HiLog.info(label, "RV_stopAnim:  5--> " +System.currentTimeMillis());
        //绘制背景
        if (isTransparentMode) {
            //启用CLEAR模式，所绘制内容不会提交到画布上。
            HiLog.info(label, "WLV_doDrawBackground 1: " +canvasbackGroundColor);
            canvas.drawColor(canvasbackGroundColor, Canvas.PorterDuffMode.CLEAR);

        } else {
            HiLog.info(label, "WLV_doDrawBackground 2: " +canvasbackGroundColor+", Color "+Color.BLUE.toString());
            canvas.drawColor(canvasbackGroundColor, Canvas.PorterDuffMode.PLUS);//此处原本是没有任何PorterDuffMode

        }
        HiLog.info(label1, "WLV_canvasbackground 2a : "+isRelease );
        if (isRelease == true){
            resetPaths();
            HiLog.info(label1, "WLV_canvasbackground 3: " +canvasbackGroundColor+", Color "+Color.BLUE.toString());
            canvas.drawColor(canvasbackGroundColor, Canvas.PorterDuffMode.PLUS);
        }

        if (isStop == true){
            canvas.drawColor(canvasbackGroundColor, Canvas.PorterDuffMode.PLUS);
            temp_paths = paths;
            resetPaths();
            for (int i = 0; i < paths.size(); i++) {
                canvas.drawPath(paths.get(i), paint);
            }
            paths = temp_paths;
            isStop = false;
        }

        if (isIsStart() == true&&isRelease == false&&isStop ==false){
            millisPassed = getMillisPassed();
            HiLog.info(label, "RV_stopAnim:  5b-> " +System.currentTimeMillis());
            float offset = millisPassed / offsetSpeed;
            if (isParametersNull()) {
                initDraw(canvas);
            }

            if (lineAnim(canvas)) {
                resetPaths();
                softerChangeVolume();

                //波形函数的值
                float curY;
                for (int i = 0; i <= samplingSize; i++) {
                    //双重判断确保必要参数正常
                    if (isParametersNull()) {
                        initDraw(canvas);
                        if (isParametersNull()) {
                            return;
                        }
                    }
                    float x = samplingX[i];
                    curY = (float) (amplitude * calcValue(mapX[i], offset));
                    for (int n = 0; n < paths.size(); n++) {
                        //四条线分别乘以不同的函数系数
                        float realY = curY * pathFuncs[n] * volume * 0.01f;
                        paths.get(n).lineTo(x, centerHeight + realY);
                    }
                }

                //连线至终点
                for (int i = 0; i < paths.size(); i++) {
                    paths.get(i).moveTo(width, centerHeight);
                }

                //绘制曲线
                for (int n = 0; n < paths.size(); n++) {
                    if (n == 0) {
                        paint.setStrokeWidth(thickLineWidth);
                        paint.setAlpha((int) (255 * alphaInAnim()));
                    } else {
                        paint.setStrokeWidth(fineLineWidth);
                        paint.setAlpha((int) (100 * alphaInAnim()));
                    }
                    canvas.drawPath(paths.get(n),paint);
                    HiLog.info(label, "RV_stopAnim:  5a-> " +System.currentTimeMillis());
                }
            }
        }
        HiLog.info(label, "RV_stopAnim:  6--> " +System.currentTimeMillis());
    }

    EventRunner runner = EventRunner.getMainEventRunner();
    public class HMEventHandler extends EventHandler{

        public HMEventHandler(EventRunner runner) throws IllegalArgumentException {
            super(runner);
        }

        @Override
        protected void processEvent(InnerEvent event) {
            super.processEvent(event);
            int id = event.eventId;
            long param = event.param;
            switch (id)
            {
                case 0:
                    isStart = true;isRelease=false; isStop = false;
                    setIsStart(isStart);
                    setMillisPassed(param);
                    invalidate();
                    break;
                case 1:
                    invalidate();
                    break;
                case 2:
                    break;
                case 3:
                    HiLog.info(label1, "WLV_release 22 ");
                    isRelease = true;
                    invalidate();
                    break;
                default:
                    break;
            }
        }
    }

    private HMEventHandler mHMEventHandler;
    @Override
    protected void onRender(Canvas canvas, long millisPassed) {
        float offset = millisPassed / offsetSpeed;
        HiLog.info(label, "WLV_onRender "+(canvas==null)+" paint "+(paint==null)+" offset: "+offset);
        mContext.getUITaskDispatcher().asyncDispatch(() -> {
            //更新UI的操作
            isStart = true;isRelease=false;isStop = false;
            setIsStart(isStart);
            setMillisPassed(millisPassed);
            invalidate();
        });
    }

    //检查音量是否合法
    private void checkVolumeValue() {
        if (targetVolume > 100) targetVolume = 100;
    }

    //检查灵敏度值是否合法
    private void checkSensibilityValue() {
        if (sensibility > 10) sensibility = 10;
        if (sensibility < 1) sensibility = 1;
    }

    /**
     * 使曲线振幅有较大改变时动画过渡自然
     */
    private void softerChangeVolume() {
        //这里减去perVolume是为了防止volume频繁在targetVolume上下抖动
        if (volume < targetVolume - perVolume) {
            volume += perVolume;
        } else if (volume > targetVolume + perVolume) {
            if (volume < perVolume * 2) {
                volume = perVolume * 2;
            } else {
                volume -= perVolume;
            }
        } else {
            volume = targetVolume;
        }

    }

    /**
     * 渐入动画
     *
     * @return progress of animation
     */
    private float alphaInAnim() {
        if (!isOpenPrepareAnim) return 1;
        if (prepareAlpha < 1f) {
            prepareAlpha += 0.02f;
        } else {
            prepareAlpha = 1;
        }
        return prepareAlpha;
    }

    /**
     * 连线动画
     *
     * @param canvas
     * @return whether animation is end
     */
    private boolean lineAnim(Canvas canvas) {
        if (isPrepareLineAnimEnd || !isOpenPrepareAnim) return true;
        paths.get(0).moveTo(0, centerHeight);
        paths.get(1).moveTo(width, centerHeight);

        for (int i = 1; i <= samplingSize; i++) {
            float x = 1f * i * lineAnimX / samplingSize;
            paths.get(0).lineTo(x, centerHeight);
            paths.get(1).lineTo(width - x, centerHeight);
        }

        paths.get(0).moveTo(width / 2f, centerHeight);
        paths.get(1).moveTo(width / 2f, centerHeight);

        lineAnimX += width / 60;
        canvas.drawPath(paths.get(0), paint);
        canvas.drawPath(paths.get(1), paint);

        if (lineAnimX > width / 2) {
            isPrepareLineAnimEnd = true;
            return true;
        }
        return false;
    }

    /**
     * 重置path
     */
    private void resetPaths() {
        for (int i = 0; i < paths.size(); i++) {
            paths.get(i).rewind();
            paths.get(i).moveTo(0, centerHeight);
        }
    }

    //初始化参数
    private void initParameters() {
        lineAnimX = 0;
        prepareAlpha = 0f;
        isPrepareLineAnimEnd = false;
        isPrepareAlphaAnimEnd = false;
        samplingX = null;
    }

    @Override
    public void startAnim() {
        HiLog.info(label, "WLV_startAnim");
        initParameters();
        super.startAnim();
    }

    @Override
    public void stopAnim() {isStop = true;
        super.stopAnim();
        int eventId = 1;
        long param = millisPassed;
        Object object = (Object) EventRunner.current();
        InnerEvent event = InnerEvent.get(eventId,  param, object);
        mHMEventHandler.sendEvent(event);
        mHMEventHandler.removeEvent(0);

    }
    private  Canvas mCanvas;
    //清空画布所有内容
    public void clearDraw() {
        HiLog.info(label, "WLV_clearDraw ");
        try {
            if (getSurfaceOps().get()!=null){
                mCanvas = getSurfaceOps().get().lockCanvas();
            }
            mCanvas.drawColor(canvasbackGroundColor, Canvas.PorterDuffMode.PLUS);
            resetPaths();
            for (int i = 0; i < paths.size(); i++) {
                mCanvas.drawPath(paths.get(i), paint);
            }
            HiLog.info(label, "WLV_clearDraw_onDraw 1 ");
        } catch (Exception e) {
        } finally {
            if (mCanvas != null) {
                if (getSurfaceOps().get()!=null)
                    getSurfaceOps().get().unlockCanvasAndPost(mCanvas);
            }
        }

    }

    //初始化绘制参数
    private void initDraw(Canvas canvas) {
        width = getWidth();
        height = getHeight();
        HiLog.info(label, "WLV_initDraw "+width+",  height "+height+" normal_height "+ this.height+", centerHeight "+this.centerHeight);

        if (width == 0 || height == 0 || samplingSize == 0) return;

        centerHeight = height >> 1;
        //振幅为高度的1/4
        amplitude = height / 3.0f;

        //适合View的理论最大音量值，和音量不属于同一概念
        perVolume = sensibility * 0.35f;

        //初始化采样点及映射
        //这里因为包括起点和终点，所以需要+1
        samplingX = new float[samplingSize + 1];
        mapX = new float[samplingSize + 1];
        //确定采样点之间的间距
        float gap = width / (float) samplingSize;
        //采样点的位置
        float x;
        for (int i = 0; i <= samplingSize; i++) {
            x = i * gap;
            samplingX[i] = x;
            //将采样点映射到[-2，2]
            mapX[i] = (x / (float) width) * 4 - 2;
        }

        paint.setStyle(Paint.Style.STROKE_STYLE);//没塞满,有缝隙
        paint.setColor(new Color(lineColor));
        paint.setStrokeWidth(thickLineWidth);
    }

    /**
     * 计算波形函数中x对应的y值
     * <p>
     * 使用稀疏矩阵进行暂存计算好的衰减系数值，下次使用时直接查找，减少计算量
     *
     * @param mapX   换算到[-2,2]之间的x值
     * @param offset 偏移量
     * @return [-1, 1]
     */
    private double calcValue(float mapX, float offset) {
        int keyX = (int) (mapX * 1000);
        offset %= 2;
        double sinFunc = Math.sin(Math.PI * mapX - offset * Math.PI);
        double recessionFunc = -200;
        if (recessionFuncs1.containsKey(keyX)){
            recessionFunc = recessionFuncs1.get(keyX);
        }else{
            recessionFunc = 4 / (4 + Math.pow(mapX, 4));
            recessionFuncs1.put(keyX,recessionFunc);
        }
        return sinFunc * recessionFunc;
    }

    /**
     * the wave line animation move speed from left to right
     * you can use negative number to make the animation from right to left
     * the default value is 290F,the smaller, the faster
     *
     * @param moveSpeed
     */
    public void setMoveSpeed(float moveSpeed) {
        this.offsetSpeed = moveSpeed;
    }


    /**
     * User set volume, [0,100]
     *
     * @param volume
     */
    public void setVolume(int volume) {
        if (Math.abs(targetVolume - volume) > perVolume) {
            this.targetVolume = volume;
            checkVolumeValue();
        }
    }

    public void setBackGroundColor(Color backGroundColor) {
        this.backGroundColor = backGroundColor;
        this.isTransparentMode = (backGroundColor == Color.TRANSPARENT);
    }

    public void setLineColor(int lineColor) {
        this.lineColor = lineColor;
    }

    /**
     * Sensitivity, the bigger the more sensitive [1,10]
     * the default value is 5
     *
     * @param sensibility
     */
    public void setSensibility(int sensibility) {
        this.sensibility = sensibility;
        checkSensibilityValue();
    }

    @Override
    public void release() {
        super.release();
        int eventId = 3;
        long param = millisPassed;
        Object object = (Object) EventRunner.current();
        InnerEvent event = InnerEvent.get(eventId,  param, object);
        mHMEventHandler.sendEvent(event);
    }

}
